﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using LumenWorks.Framework.IO.Csv;
using Calcite.Classes.Misc;
using System.Diagnostics;
using Calcite.Properties;
using CalciteCsv;

namespace Calcite.Dialogs
{
    public partial class ImportFileDialog : Form
    {
        //TODO: Tidy up this class, bound to be some cruft after refactoring

        #region Public properties

        /// <summary>
        /// Filenames to import
        /// </summary>
        public string [] FileNames;

        /// <summary>
        /// The CsvSpec instance (from CalciteCsv project) loaded 
        /// with relevant parameters
        /// </summary>
        public CsvSpec CsvSpec;

        #endregion

        public ImportFileDialog(string[] filenames)
        {
            InitializeComponent();

            // TODO: Populate the combo box with Import Templates
            this.FileNames = filenames;

            // By default make it a tab separated file
            this.CsvSpec = new CalciteCsv.CsvSpec(CalciteCsv.CsvTypes.TabSeperatedFile);

            int i;
            // Populate the filenames checked list box and set to checked
            this.filenamesCheckedListBox.Items.AddRange(this.FileNames);
            for (i = 0; i < this.filenamesCheckedListBox.Items.Count; i++)
            {
                this.filenamesCheckedListBox.SetItemChecked(i, true);
            }
            // Link the radiobuttons to their values
            this.otherDelimiterRadioButton.Tag = this.otherDelimiterTextBox.Text;
            this.fixedWidthFormatRadioButton.Tag = this.fixedWidthFormatTextBox.Text;
            this.tabDelimiterRadioButton.Tag = "\t";
            this.commaDelimiterRadioButton.Tag = ",";
            // Set the colours of the boxes according to the previewGridView scheme
            this.headerLineTextBox.BackColor = this.previewGridView.headerRowStyle.BackColor;
            this.unitsLineTextBox.BackColor = this.previewGridView.unitsRowStyle.BackColor;
            // Set the styles of the manual input rows
            //this.manualUnitsInputDataGridViewRow.DefaultCellStyle = this.previewGridView.unitsRowStyle;
            //this.manualHeadersInputDataGridViewRow.DefaultCellStyle = this.previewGridView.headerRowStyle;
            //this.manualUnitsInputDataGridViewRow.ReadOnly = false;
            //this.manualHeadersInputDataGridViewRow.ReadOnly = false;
            // Populate the preview
            this.UpdatePreview();
        }

        #region Form-wide cache variables

        /// <summary>
        /// Get the chosen delimiter string. Is null if fixed width chosen
        /// </summary>
        internal string Delimiter
        {
            get {
                if (this.fixedWidthFormatRadioButton.Checked)
                {
                    return String.Empty;
                }
                else if (this.otherDelimiterRadioButton.Checked)
                {
                    return this.otherDelimiterTextBox.Text;
                    //char result;
                    //if (Char.TryParse(this.otherDelimiterTextBox.Text, out result))
                    //{
                    //    return result;
                    //}
                    //else
                    //{
                    //    // Danger Will Robinson
                    //    return null;
                    //}
                }
                else
                {
                    foreach (Control ctl in Functions.GetAllChildControls(this.columnDelimiterGroupBox))
                    {
                        if (ctl is RadioButton)
                        {
                            RadioButton radioBtn = (RadioButton)ctl;
                            if (radioBtn.Checked)
                            {
                                //return (char)radioBtn.Tag;
                                return (string)radioBtn.Tag;
                            }
                        }
                    }
                    // Nothing selected
                    return String.Empty;
                }
            }
        }

        /// <summary>
        /// Gets the chosen quote character 
        /// </summary>
        internal string QuoteString
        {
            get
            {
                // TODO: Put this in the form?
                return "\"";
            }
        }

        /// <summary>
        /// Gets chosen escape character
        /// </summary>
        internal string EscapeString
        {
            get
            {
                // Put this in form?
                return "\\";
            }
        }

        internal string CommentString
        {
            get
            {
                // Put this in form?
                return "#";
            }
        }

        internal List<int> FixedWidthFormat
        {
            get
            {
                //return this.fixedWidthFormatTextBox.Text;
                // TODO: Write a method to parse this out
                return new List<int>() { };

            }
        }

        internal bool IsFixedWidth
        {
            get
            {
                return this.fixedWidthFormatRadioButton.Checked;
                
            }
        }

        internal string SkippedRowsFormat
        {
            get
            {
                return this.rowsToSkipTextBox.Text;
            }
        }

        /// <summary>
        /// Gets the line number that corresponds to the units line
        /// </summary>
        internal int UnitsLineIndex
        {
            get
            {
                int result;
                if (Int32.TryParse(this.unitsLineTextBox.Text.Trim(), out result))
                {
                    return result - 1;
                }
                else
                {
                    // Avoid null return - probably not optimal
                    return -1;
                }

            }
        }

        /// <summary>
        /// Gets the line number that corresponds to the header line
        /// </summary>
        internal int HeaderLineIndex
        {
            get
            {
                int result;
                if (Int32.TryParse(this.headerLineTextBox.Text.Trim(), out result))
                {
                    return result - 1;
                }
                else
                {
                    // Avoid null return - probably not optimal
                    return -1;
                }
                
            }
        }

        /// <summary>
        /// Gets a list of integers corresponding to the rows that need to be 
        /// skipped, may contain duplicates
        /// </summary>
        internal List<int> IndexesOfSkippedRows
        {
            get
            {
                int i;
                List<int> invalidRows = Functions.ParseRangeFormat(this.rowsToSkipTextBox.Text);
                // Add header and units line to the rows to be skipped since they also contain text
                // This may cause duplication, but then so can the ParseRangeFormatFunction
                for (i = 0; i < invalidRows.Count; i++)
                {
                    invalidRows[i] = invalidRows[i] - 1;
                }
                if (this.HeaderLineIndex != -1) 
                {
                    invalidRows.Add(this.HeaderLineIndex);
                }
                if (this.UnitsLineIndex != -1)
                {
                    invalidRows.Add(this.UnitsLineIndex);
                }
                return invalidRows.Distinct().ToList<int>();
            }
        }

        /// <summary>
        /// Gets empty only if no headers are specified manually or in file
        /// </summary>
        internal List<string> Headers = new List<string>();

        /// <summary>
        /// Gets empty only if no units are specified manually or in file
        /// </summary>
        internal List<string> Units = new List<string>();

        /// <summary>
        /// Whether or not to include the manual input header row
        /// </summary>
        private bool ShowManualHeaderRow
        {
            get
            {
                return this.manualHeaderCheckBox.Checked;
            }
        }

        /// <summary>
        /// Whether or not to include the manual input units row
        /// </summary>
        private bool ShowManualUnitsRow 
        {
            get
            {
                return this.manualUnitsCheckBox.Checked;
            }
        }

        /// <summary>
        /// Used to store the value of the headerLineTextBox whilst manual entry is activated
        /// </summary>
        private string HeaderLineCache = String.Empty;

        /// <summary>
        /// Used to store the value of the unitsLineTextBox whilst manual entry is activated
        /// </summary>
        private string UnitsLineCache = String.Empty;

        //private DataGridViewRow manualHeadersInputDataGridViewRow = new DataGridViewRow();
        //private DataGridViewRow manualUnitsInputDataGridViewRow = new DataGridViewRow();

        /// <summary>
        /// Stores the manually entered headers
        /// </summary>
        private List<string> ManualHeaderRowCache = new List<string>();

        /// <summary>
        /// Stores the manually entered units 
        /// </summary>
        private List<string> ManualUnitsRowCache = new List<string>();

        #endregion

        #region 'Is' methods

        /// <summary>
        /// Returns true if this row index (zero based) corresponds to user specified header row (1 based)
        /// </summary>
        /// <param name="rowIndex">Row index (zero based)</param>
        /// <returns></returns>
        private bool IsHeaderRow(int rowIndex)
        {
            return rowIndex == this.HeaderLineIndex;
        }

        /// <summary>
        /// Returns true if this row index (zero based) corresponds to user specified units row (1 based)
        /// </summary>
        /// <param name="rowIndex">Row index (zero based)</param>
        /// <returns></returns>
        private bool IsUnitsRow(int rowIndex)
        {
            return rowIndex == this.UnitsLineIndex;
        }

        /// <summary>
        /// Determines whether a row number corresponds to a manual input row for headers
        /// </summary>
        /// <param name="index">Row number (0 indexed)</param>
        /// <returns>True if row is manual input row for headers</returns>
        private bool IsManualHeaderRow(int index)
        {
            if ((this.ShowManualHeaderRow == true) && (index == 0))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Determines whether an row number is a manual input row for units
        /// </summary>
        /// <param name="row">Row number (0 indexed)</param>
        /// <returns>True if row is manual input row for units</returns>
        private bool IsManualUnitsRow(int row)
        {
            if (((this.ShowManualHeaderRow && this.ShowManualUnitsRow) && (row == 1))
                || (this.ShowManualUnitsRow && (this.ShowManualHeaderRow == false) && (row == 0)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region Concurrency code

        /// <summary>
        /// Updates the attached CsvSpec object
        /// </summary>
        internal void UpdateSpec()
        {
            this.CsvSpec.ColumnDelimiter = this.Delimiter;
            this.CsvSpec.CommentString = this.CommentString;
            this.CsvSpec.EscapeString = this.EscapeString;
            this.CsvSpec.FixedWidthColumnWidths = this.FixedWidthFormat;
            this.CsvSpec.HeaderRow = this.HeaderLineIndex;
            this.CsvSpec.UnitsRow = this.UnitsLineIndex;
            this.CsvSpec.Units = this.Units;
            this.CsvSpec.Headers = this.Headers;
            this.CsvSpec.IsFixedWidth = this.IsFixedWidth;
            this.CsvSpec.QuoteString = this.QuoteString;
            this.CsvSpec.RowsToSkipFormat = this.SkippedRowsFormat;
        }


        /// <summary>
        /// Updates the preview box using the form values
        /// </summary>
        internal void UpdatePreview()
        {
            // TODO: Use the selected filename for the preview
            int i, j;
            int filenameIndex = 0;
            string filename = this.filenamesCheckedListBox.Items[filenameIndex].ToString();

            this.UpdateSpec();

            if (this.CsvSpec.IsValid())
            {
                // Reset the grid
                this.previewGridView.Rows.Clear();
                this.previewGridView.Columns.Clear();

                // Unbind the cellValuesChanged event so isn't triggered after 
                // every cell updated programtically
                this.previewGridView.CellValueChanged -= new System.Windows.Forms.DataGridViewCellEventHandler(this.previewGridView_CellValueChanged);



                using (CalciteCsv.CsvReader csvReader = new CalciteCsv.CsvReader(new StreamReader(filename), this.CsvSpec)) 
                {
                    // Set the GridView row template
                    DataGridViewRow previewGridViewRowTemplate = this.previewGridView.RowTemplate;
                    previewGridViewRowTemplate.DefaultCellStyle.NullValue = String.Empty;
                    

                    // Create the rows and set their attributes
                    for (i = 0; i < ApplicationSettings.Default.NumCsvPreviewRows; i++)
                    {
                        DataGridViewRow newRow = new DataGridViewRow();                        
                        // Include the manual input rows if necessary
                        if (this.IsManualHeaderRow(i))
                        {
                            // Add at least one column
                            this.previewGridView.ExpandColumns(Math.Max(this.ManualHeaderRowCache.Count, 1));
                            newRow.DefaultCellStyle = this.previewGridView.headerRowStyle;
                            newRow.ReadOnly = false;
                            this.previewGridView.Rows.Add(newRow);
                            Functions.FillDataGridViewRow(this.previewGridView.Rows[i],
                                this.ManualHeaderRowCache);
                        }
                        else if (this.IsManualUnitsRow(i))
                        {
                            // Add at least one column
                            this.previewGridView.ExpandColumns(Math.Max(this.ManualUnitsRowCache.Count, 1));
                            newRow.DefaultCellStyle = this.previewGridView.unitsRowStyle;
                            newRow.ReadOnly = false;
                            this.previewGridView.Rows.Add(newRow);
                            Functions.FillDataGridViewRow(this.previewGridView.Rows[i],
                                this.ManualUnitsRowCache);
                        }
                        else
                        {
                            // First configure the look of the row
                            newRow.ReadOnly = true;
                            // Do stuff on reaching a row to be skipped - note will be overwritten  
                            // by a header line or units line. This is by design
                            if (this.IndexesOfSkippedRows.Exists(x => x == i))
                            {
                                newRow.DefaultCellStyle = this.previewGridView.skippedRowStyle;
                            }
                            // Configure the headers row                          
                            if (this.IsHeaderRow(i))
                            {
                                newRow.DefaultCellStyle = this.previewGridView.headerRowStyle;
                            }
                            // Configure the units line
                            else if (this.IsUnitsRow(i))
                            {
                                newRow.DefaultCellStyle = this.previewGridView.unitsRowStyle;
                            }
                            
                            if (csvReader.ReadNextRow() == true)
                            {
                                this.previewGridView.ExpandColumns(Math.Max(csvReader.Columns.Count, 1));
                                this.previewGridView.Rows.Add(newRow);
                                j = 0;
                                foreach (string colValue in csvReader.Columns)
                                {
                                    this.previewGridView.Rows[i].Cells[j].Value = csvReader.Columns[j];
                                    j = j + 1;
                                }
                            }
                            else
                            {
                                for (j = 0; j < this.previewGridView.Columns.Count; j++)
                                {
                                    this.previewGridView.Rows[i].Cells[j].Value = String.Empty;
                                }
                            }
                            
                        }
                    }
                    this.UpdateHeadersUnits();
                    // Don't need selection
                    this.previewGridView.ClearSelection();
                    // Rebind cellChanged event
                    this.previewGridView.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.previewGridView_CellValueChanged);
                
                } // Dispose csvReader
            }
            else
            {
                // Spec is invalid. Do nothing?
            }

        }

        /// <summary>
        /// Reads data from datagridview cells into the headers and units cache
        /// </summary>
        private void UpdateHeadersUnits()
        {
            if (this.HeaderLineIndex > -1 &&
                this.HeaderLineIndex < this.previewGridView.Rows.Count)
            {
                this.Headers = Functions.CacheDataGridViewRow(this.previewGridView.Rows[this.HeaderLineIndex]);
            }
            if (this.UnitsLineIndex > -1 &&
                this.UnitsLineIndex < this.previewGridView.Rows.Count)
            {
                this.Units = Functions.CacheDataGridViewRow(this.previewGridView.Rows[this.UnitsLineIndex]);
            }
            if (this.ShowManualHeaderRow)
            {
                Debug.Assert(this.HeaderLineIndex == 0);
                this.ManualHeaderRowCache = this.Headers.ToList<string>();
            }
            if (this.ShowManualUnitsRow)
            {
                Debug.Assert(this.UnitsLineIndex == 0 || this.UnitsLineIndex == 1);
                this.ManualUnitsRowCache = this.Units.ToList<string>();
            }
        }

        #endregion

        #region Routine form behaviour

        private void previewGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            if (this.IsManualHeaderRow(e.RowIndex) || this.IsManualUnitsRow(e.RowIndex))
            {
                this.UpdateHeadersUnits();
            }
        }

        private void importButton_Click(object sender, EventArgs e)
        {
            // Rewrite the filenames only to show those that are still selected
            List<string> tmpFileNames = new List<string>();
            foreach (object fileName in this.filenamesCheckedListBox.CheckedItems)
            {
                tmpFileNames.Add(fileName.ToString());
            }
            this.FileNames = tmpFileNames.ToArray();

            // Configure the CsvSpec with the last inputted values
            this.UpdateSpec();

        }

        private void otherDelimiterRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            // Move focus to the corresponding textbox
            if (otherDelimiterRadioButton.Checked == true)
            {
                this.UpdatePreview();
                otherDelimiterTextBox.Focus();
            }
        }

        private void fixedWidthFormatRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            // Move focus to the corresponding textbox
            if (fixedWidthFormatRadioButton.Checked == true)
            {
                this.UpdatePreview();
                fixedWidthFormatTextBox.Focus();
            }
        }

        private void headerLineTextBoxLabel_Enter(object sender, EventArgs e)
        {
            // Move focus to the textbox
            headerLineTextBox.Focus();
        }

        private void unitsLineTextBoxLabel_Enter(object sender, EventArgs e)
        {
            // Move focus to the textbox
            unitsLineTextBox.Focus();
        }

        private void rowsToSkipTextBoxLabel_Enter(object sender, EventArgs e)
        {
            // Move focus to the textbox
            rowsToSkipTextBox.Focus();
        }

        private void templateNamesComboBoxLabel_Enter(object sender, EventArgs e)
        {
            // Move focus to the textbox
            templateNamesComboBox.Focus();
        }

        private void rowsToSkipTextBox_TextChanged(object sender, EventArgs e)
        {
            this.UpdatePreview();
        }

        private void headerLineTextBox_TextChanged(object sender, EventArgs e)
        {
            if (headerLineTextBox.Enabled == true)
            {
                this.UpdatePreview();
            }
        }

        private void unitsLineTextBox_TextChanged(object sender, EventArgs e)
        {
            if (unitsLineTextBox.Enabled == true)
            {
                this.UpdatePreview();
            }
        }

        private void tabDelimiterRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            // Incldue this so update is only fired once
            if (tabDelimiterRadioButton.Checked == true)
            {
                this.UpdatePreview();
            }
        }

        private void commaDelimiterRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            // Incldue this so update is only fired once
            if (this.commaDelimiterRadioButton.Checked == true)
            {
                this.UpdatePreview();
            }
        }

        private void otherDelimiterTextBox_TextChanged(object sender, EventArgs e)
        {
            this.UpdatePreview();
        }

        private void manualUnitsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            if (manualUnitsCheckBox.Checked == true)
            {
                this.EnterEditUnitsMode();
            }
            else
            {
                this.LeaveEditUnitsMode();
            }
        }

        private void manualHeaderCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            if (manualHeaderCheckBox.Checked == true)
            {
                EnterEditHeadersMode();
            }
            else
            {
                LeaveEditHeadersMode();
            }
        }

        #endregion

        #region Form state code

        private void LeaveEditHeadersMode()
        {
            this.UpdateHeadersUnits();
            this.manualHeaderCheckBox.Checked = false;
            // Make the units entry the top line 
            if (this.manualUnitsCheckBox.Checked == true)
            {
                this.unitsLineTextBox.Text = "1";
            }
            // Make text box is disabled when changing text otherwise triggers an update
            this.headerLineTextBox.Text = this.HeaderLineCache;
            this.headerLineTextBox.Enabled = true;
            this.UpdatePreview();
        }

        private void EnterEditHeadersMode()
        {
            this.manualHeaderCheckBox.Checked = true;
            // Store the current value in case want to go back later
            this.HeaderLineCache = this.headerLineTextBox.Text;
            this.headerLineTextBox.Enabled = false;
            this.headerLineTextBox.Text = "1";
            if (this.manualUnitsCheckBox.Checked == true)
            {
                this.unitsLineTextBox.Text = "2";
            }
            this.UpdatePreview();
            // Place focus in first headers cell
            if (this.previewGridView.Rows[0].Cells.Count > 0)
            {
                this.previewGridView.CurrentCell = this.previewGridView.Rows[0].Cells[0];
                this.previewGridView.BeginEdit(true);
            }
        }

        private void LeaveEditUnitsMode()
        {
            this.UpdateHeadersUnits();
            this.manualUnitsCheckBox.Checked = false;
            // Make text box is disabled when changing text otherwise triggers an update
            this.unitsLineTextBox.Text = this.UnitsLineCache;
            this.unitsLineTextBox.Enabled = true;
            this.UpdatePreview();
        }

        private void EnterEditUnitsMode()
        {
            int manualEditUnitsLine;
            this.manualUnitsCheckBox.Checked = true;
            // Store the current value in case want to go back later
            this.UnitsLineCache = this.unitsLineTextBox.Text;
            // Units entry may be 1 or 2 depending on whether there is a header line
            if (this.ShowManualHeaderRow)
            {
                manualEditUnitsLine = 2;
            }
            else
            {
                manualEditUnitsLine = 1;
            }
            this.unitsLineTextBox.Enabled = false;
            this.unitsLineTextBox.Text = manualEditUnitsLine.ToString();
            this.UpdatePreview();
            // Place focus in first units cell
            if (this.previewGridView.Rows[manualEditUnitsLine - 1].Cells.Count > 0)
            {
                this.previewGridView.CurrentCell = this.previewGridView.Rows[manualEditUnitsLine -1].Cells[0];
                this.previewGridView.BeginEdit(true);
            }
        }

        #endregion

    }
}
